
package com.joysuch.wwyt.audit.service.impl;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;

import javax.servlet.http.HttpServletRequest;

import com.joysuch.wwyt.audit.bean.AuditLogContext;
import com.joysuch.wwyt.audit.entity.AuditLogActionDef;
import com.joysuch.wwyt.audit.entity.AuditLogRecord;
import com.joysuch.wwyt.audit.service.AuditLogActionDefService;
import com.joysuch.wwyt.audit.service.AuditLogContextHolder;
import com.joysuch.wwyt.audit.service.AuditLogGenerator;
import com.joysuch.wwyt.audit.service.AuditLogProcessor;
import com.joysuch.wwyt.audit.service.EntityResolver;
import com.joysuch.wwyt.audit.service.ReturnResultParser;
import com.joysuch.wwyt.core.common.ajax.ResultBean;
import com.joysuch.wwyt.core.common.util.HttpUtils;
import com.joysuch.wwyt.core.service.BizCodeGeneratorFacade;
import com.joysuch.wwyt.core.support.Context;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

@Aspect
@Configuration
public class AopAuditLogGeneratorImpl implements AuditLogGenerator {

    private static final Logger logger = LoggerFactory.getLogger(AopAuditLogGeneratorImpl.class);

    @Autowired
    private AuditLogActionDefService auditLogActionDefService;
    @Autowired
    private BizCodeGeneratorFacade bizCodeGeneratorFacade;
    @Autowired
    private EntityResolver entityResolver;
    @Autowired
    private AuditLogProcessor auditLogProcessor;

    @Pointcut("execution(public * com.joysuch.wwyt..controller..*(..))")
    public void generate2() {
    }

    public AuditLogRecord generate() {
        return null;
    }

    @Before("generate2()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {

    }

    @Around("generate2()")
    public Object myAround(ProceedingJoinPoint proceedingJoinPoint)   throws Throwable  {

        long s = System.currentTimeMillis();
        AuditLogActionDef def = null;
        AuditLogContext context = null;
        Object obj = null;
        try {
            def = findActionDef(proceedingJoinPoint);
            if (def != null) {
                context = parseContextBeforeProcess(proceedingJoinPoint, def);
                if (context.isDeleteOp()) {
                    // 删除操作,操作前获取操作对象
                    updateContextInfo(context);
                }
            }
            long s1 = System.currentTimeMillis();
            obj = proceedingJoinPoint.proceed();
            long s2 = System.currentTimeMillis();
            if (def != null) {
                if (!context.isDeleteOp()) {
                    // 非删除方法
                    updateContextInfo(context);
                }
                // 记录日志
                operationLogByReturnValue(def, context, obj);
            }
            long e = System.currentTimeMillis();
            logger.debug(String.format("拦截共花费%d+%d=%dms", s1 - s, e - s2, (e - s2 + s1 - s)));
            return obj;
        } catch (Throwable e) {
            logger.error("审计日志记录异常", e);
            if (def != null) {
                operationFailedByException(def, context, e);
            }
            throw e;
        } finally {
            AuditLogContextHolder.remove();
        }

    }

    private void updateContextInfo(AuditLogContext context) {
        try {
            if ("param".equals(context.getQueryEntityMethod())) {
                Object value = context.getParamValue();

                if (value != null) {
                    if (value instanceof List) {
                        // id 硬编码待调整
                        List list = entityResolver.getByProp(context.getEntityClass(), "id",
                                (List) value);
                        context.setObject(list);
                    } else if (value.getClass().isArray()) {
                        Object[] cache = new Object[Array.getLength(value)];
                        for (int i = 0; i < Array.getLength(value); i++) {
                            cache[i] = Array.get(value, i);
                        }
                        List list = entityResolver.getByProp(context.getEntityClass(), "id", cache);
                        context.setObject(list);
                    } else if (value instanceof Number) {
                        // 默认只选择一个实体
                        List list = entityResolver.getByProp(context.getEntityClass(), "id", value);
                        context.setObject(list.size() == 1 ? list.get(0) : list);
                    }
                }
            } else if ("context".equals(context.getQueryEntityMethod())) {
                List<Object> list = AuditLogContextHolder.get();
                // 从操作堆栈中找到合适的值
                Object value = findSpecificValueFromContext(list, context);
                context.setObject(value);
                if (value != null) {
                    // 从上下文中找出参数值，本方法一般用于新增操作
                    queryParamValueFromContext(context);
                }

            } else {
            }
        }catch (Throwable e){
            logger.error("审计日志记录异常", e);
        }
    }

    private Object findSpecificValueFromContext(List<Object> list, AuditLogContext context) {
        if (list == null || list.isEmpty()) {
            return null;
        }
        for (Object obj : list) {
            if (obj == null) {
                continue;
            }
            // 元素类型匹配
            if (obj.getClass() == context.getEntityClass()) {
                return obj;
            }
            // 集合元素，有一个类型匹配
            if (obj instanceof Collection) {
                for (Object v : ((Collection) obj)) {
                    if (v.getClass() == context.getEntityClass()) {
                        return obj;
                    }
                }
            }
            // 数组元素
            if (obj.getClass().isArray()) {
                for (int i = 0; i < Array.getLength(obj); i++) {
                    Object v = Array.get(obj, i);
                    if (v.getClass() == context.getEntityClass()) {
                        return obj;
                    }
                }
            }
        }
        return null;
    }

    private void queryParamValueFromContext(AuditLogContext context) {
        Object value = context.getObject();
        if (value instanceof List) {
            // id 硬编码待调整
            List list = (List) value;
            if (context.getParamName() == null) {
                context.setParamValue(value);
                return;
            }
            List<Object> re = new ArrayList<>(list.size());
            for (Object oj : list) {
                re.add(extractChainValueFromArg(oj, context.getParamName()));
            }
            context.setParamValue(re);
        } else if (value.getClass().isArray()) {
            if (context.getParamName() == null) {
                context.setParamValue(value);
                return;
            }
            List<Object> re = new ArrayList<>(Array.getLength(value));
            for (int i = 0; i < Array.getLength(value); i++) {
                re.add(extractChainValueFromArg(Array.get(value, i), context.getParamName()));
            }
            context.setParamValue(re);
        } else {
            // 默认只选择一个实体
            if (context.getParamName() == null) {
                context.setParamValue(value);
                return;
            }
            context.setParamValue(extractChainValueFromArg(value, context.getParamName()));
        }
    }

    private AuditLogContext parseContextBeforeProcess(ProceedingJoinPoint proceedingJoinPoint, AuditLogActionDef def) {
        AuditLogContext context = new AuditLogContext();
        try {
            context.setEntityClass(loadClass(def.getOpObjectType()));
            context.setQueryEntityMethod(def.getQueryObjectMethod());
            context.setParamName(def.getQueryObjectParams());
            if ("param".equals(def.getQueryObjectMethod())) {
                context.setNameField(def.getOpObjectNameAttr());
                context.setParamValue(getValueFromParams(proceedingJoinPoint, def.getQueryObjectParams()));
            } else if ("context".equals(def.getQueryObjectMethod())) {
                AuditLogContextHolder.set(context);
                context.setNameField(def.getOpObjectNameAttr());
            } else {
                logger.warn("不识别的参数解析方法 {}", def.getQueryObjectMethod());
            }
        }catch (Throwable e){
            logger.error("审计日志获取 context 异常",e);
        }
        context.setDeleteOp("D".equalsIgnoreCase(def.getOpType()));
        return context;
    }

    private Object getValueFromParams(ProceedingJoinPoint proceedingJoinPoint, String queryObjectParams) {
        MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();

        Method method = methodSignature.getMethod();
        Parameter[] parameters = method.getParameters();
        boolean multiLevelParamName = queryObjectParams.contains(".");
        String pname = queryObjectParams;
        String pnameSuffix = queryObjectParams;
        if (multiLevelParamName) {
            pname = queryObjectParams.substring(0, queryObjectParams.indexOf('.'));
            pnameSuffix = queryObjectParams.substring(queryObjectParams.indexOf('.') + 1);
        }
        for (int i = 0; i < parameters.length; i++) {
            Parameter p = parameters[i];
            if (p.getName().equals(pname)) {
                if (!multiLevelParamName) {
                    // 只有一级参数，直接返回
                    return proceedingJoinPoint.getArgs()[i];
                }
                return extractChainValueFromArg(proceedingJoinPoint.getArgs()[i], pnameSuffix);
            }
        }
        return null;
    }

    private Object extractChainValueFromArg(Object object, String queryObjectParams) {
        if (object == null) {
            return object;
        }
        String[] ladders = queryObjectParams.split("\\.");
        for (int j = 0; j < ladders.length; j++) {
            object = getChainPropertyValue(object, ladders[j]);
            if (object == null) {
                return null;
            }
        }
        // 获取链式值
        return object;
    }

    private Object getChainPropertyValue(Object object, String substring) {
        PropertyDescriptor pd = BeanUtils.getPropertyDescriptor(object.getClass(), substring);
        if (pd != null && pd.getReadMethod() != null) {
            try {
                return pd.getReadMethod().invoke(object, null);
            } catch (Exception e) {
                logger.error("获取属性值出错{}, {}", object, substring);
            }
        }
        return null;
    }

    private Class loadClass(String opObjectType) {
        if (opObjectType == null || StringUtils.isBlank(opObjectType)) {
            return null;
        }
        try {
            return Class.forName(opObjectType);
        } catch (Exception e) {
            logger.error("找不到类" + opObjectType, e);
        }
        return null;
    }

    private void operationLogByReturnValue(AuditLogActionDef def, AuditLogContext context, Object obj) {
        try{
            if (Context.getCurrentUserId() == null) {
                // 未登录状态,不予记录
                return;
            }
            AuditLogRecord r = new AuditLogRecord();
            r.setActionDefId(def.getId());
            r.setCode(String.valueOf(new Date().getTime()));
            r.setHappenTime(new Date());
            r.setIp(parseIp());
            r.setMac(null);
            r.setModule(def.getModule());
            r.setOperationName(def.getOperationName());
            // 判断执行状态
            r.setResult(judgeSuccessState(def, context, obj));
            r.setUserId(Context.getCurrentUserId());
            r.setUserName(Context.getCurrentUser().getRealName());
            r.setOpObjectType(def.getOpObjectType());
            if(context != null){
                r.setOpObjectValue(context.getParamValue() == null ? null : context.getParamValue().toString());
                r.setDescription(groupDescription(def.getOperationName(), getEntityName(context.getObject(), context)));
            }
            auditLogProcessor.process(r);
        }catch (Throwable e) {
            logger.error("审计日志记录异常", e);
        }
    }

    private String groupDescription(String operationName, String opObjectValue) {
        if (opObjectValue != null) {
            return operationName + ":" + opObjectValue;
        }
        return operationName;
    }

    private Byte judgeSuccessState(AuditLogActionDef def, AuditLogContext context, Object obj) {
        if ("ResultBean".equals(def.getResultJudgeType())) {
            if (obj instanceof ResultBean) {
                return ((ResultBean) obj).getCode() == 0 ? (byte) 0 : (byte) -1;
            } else {
                // 不识别的参数值
                return 40;
            }
        } else if ("threadlocal".equals(def.getResultJudgeType())) {
            return AuditLogContextHolder.isSuccess() ? (byte) 0 : -1;
        } else if ("exception".equals(def.getResultJudgeType())) {
            return (byte) 0;
        } else {
            ReturnResultParser parser = parseReturnResultParser(def);
            if (parser != null) {
                return parser.parse(obj);
            }
        }
        // 不知如何解析
        return -2;
    }

    private ReturnResultParser parseReturnResultParser(AuditLogActionDef def) {
        try {
            if (def.getResultJudgeRule() != null) {
                return (ReturnResultParser) Class.forName(def.getResultJudgeRule()).newInstance();
            }
        } catch (Exception e) {
            logger.error("加载结果判断器错误:" + def.getResultJudgeRule(), e);
        }
        return null;
    }

    private void operationFailedByException(AuditLogActionDef def, AuditLogContext context, Throwable e) {
        if (Context.getCurrentUserId() == null) {
            // 未登录状态,不予记录
            return;
        }
        AuditLogRecord r = new AuditLogRecord();
        r.setActionDefId(def.getId());
        r.setCode(String.valueOf(new Date().getTime()));
        r.setHappenTime(new Date());
        r.setIp(parseIp());
        r.setMac(null);
        r.setModule(def.getModule());
        r.setOperationName(def.getOperationName());
        r.setResult((byte) -1);
        r.setUserId(Context.getCurrentUserId());
        r.setUserName(Context.getCurrentUser().getRealName());
        r.setOpObjectType(def.getOpObjectType());
        if(context != null){
            r.setOpObjectValue(context.getParamValue() == null ? null : String.valueOf(context.getParamValue()));
        }
        r.setDescription("异常");  //r.setDescription(e.getMessage());

        auditLogProcessor.process(r);
    }

    private String parseValueFromContext(AuditLogContext context) {
        if (context == null || context.getObject() == null) {
            return null;
        }
        Object v = context.getObject();
        if (v instanceof List) {
            StringBuilder sb = new StringBuilder();
            List list = (List) v;
            for (Object obj : list) {
                sb.append(getEntityName(obj, context)).append(",");
            }
            if (sb.length() > 0) {
                sb.deleteCharAt(sb.length() - 1);
            }
            return sb.toString();
        } else {
            return getEntityName(v, context);
        }
    }

    private String getEntityName(Object v, AuditLogContext context) {
        if (v == null) {
            return null;
        }
        PropertyDescriptor pd = BeanUtils.getPropertyDescriptor(v.getClass(), context.getNameField());
        if (pd != null) {
            pd.getReadMethod().setAccessible(true);
            Object ret;
            try {
                ret = pd.getReadMethod().invoke(v, new Object[0]);
                return ret == null ? null : String.valueOf(ret);
            } catch (Exception e) {
                logger.error("获取实体名称出错:" + v + "-" + context, e);
                e.printStackTrace();
            }
        }
        return null;
    }

    private String parseIp() {
        // 接收到请求，记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        return HttpUtils.getClientIp(request);
    }

    private AuditLogActionDef findActionDef(ProceedingJoinPoint proceedingJoinPoint) {
        AuditLogActionDef def = findActionDefByHttpUri(proceedingJoinPoint);
        if (def != null) {
            return def;
        }
        return findActionDefByAnnotation(proceedingJoinPoint);
    }

    private AuditLogActionDef findActionDefByAnnotation(ProceedingJoinPoint proceedingJoinPoint) {
        MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();

        Method method = methodSignature.getMethod();
        RequestMapping a = (RequestMapping) proceedingJoinPoint.getTarget().getClass()
                .getAnnotation(RequestMapping.class);
        if (a == null) {
            // 不是controller
            return null;
        }
        String[] prefixes = a.value();
        String[] suffixes = findMethodRequestMapping(method);
        String[] keys = groupKeys(prefixes, suffixes);
        return findActionDefByUrl(keys);
    }

    private String[] findMethodRequestMapping(Method method) {
        String[] suffixes = null;
        RequestMapping ma = method.getAnnotation(RequestMapping.class);
        if (ma != null) {
            suffixes = ma.value();
        } else if ((method.getAnnotation(PostMapping.class)) != null) {
            suffixes = method.getAnnotation(PostMapping.class).value();
        } else if ((method.getAnnotation(GetMapping.class)) != null) {
            suffixes = method.getAnnotation(GetMapping.class).value();
        } else if ((method.getAnnotation(PutMapping.class)) != null) {
            suffixes = method.getAnnotation(PutMapping.class).value();
        } else if ((method.getAnnotation(DeleteMapping.class)) != null) {
            suffixes = method.getAnnotation(DeleteMapping.class).value();
        }
        return suffixes == null ? new String[0] : suffixes;
    }

    private AuditLogActionDef findActionDefByHttpUri(ProceedingJoinPoint proceedingJoinPoint) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        return findActionDefByUrl(new String[] { request.getRequestURI() });
    }

    private AuditLogActionDef findActionDefByUrl(String[] keys) {
        for (String k : keys) {
            AuditLogActionDef action = auditLogActionDefService.findOneByTriggerTypeAndValue("url", k);
            logger.debug(k + ":" + action);
            if (action != null) {
                return action;
            }
        }
        return null;
    }

    private String[] groupKeys(String[] prefixes, String[] suffixes) {
        if (prefixes.length > 0 && suffixes.length > 0) {
            String[] re = new String[prefixes.length * suffixes.length];
            for (int i = 0; i < prefixes.length; i++) {
                for (int j = 0; j < prefixes.length; j++) {
                    re[i * j + j] = concat(prefixes[i], suffixes[j]);
                }
            }
            return re;
        } else {
            return prefixes.length > 0 ? prefixes : suffixes;
        }
    }

    private String concat(String pre, String suf) {
        // 去除潜在的重复路径
        return (pre + "/" + suf).replace("//", "/");
    }

}
